/* Copyright (C) 2014 InfiniDB, Inc.

   This program is free software; you can redistribute it and/or
   modify it under the terms of the GNU General Public License
   as published by the Free Software Foundation; version 2 of
   the License.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
   MA 02110-1301, USA. */

/*
 * $Id: we_dbrootextenttracker.h 4631 2013-05-02 15:21:09Z dcathey $
 */

/** @file we_dbrootextenttracker.h
 * Contains classes to track the order of placement (rotation) of extents as
 * they are filled in and/or added to the DBRoots for the local PM.
 *
 * DBRootExtentTracker did select the next DBRoot and segment number when-
 * ever either selectFirstSegFile() or nextSegFile() were called.  The logic
 * for selecting a "new" segment file number previously in nextSegFile() has
 * been moved to the DBRM extent allocation function.  The segment number
 * argument returned by nextSegFile() is now only applicable if the return
 * value is false, indicating that a partially filled extent has been en-
 * countered.
 */

#pragma once

#include <boost/thread/mutex.hpp>
#include <vector>

#include "we_type.h"
#include "brmtypes.h"

#define EXPORT

namespace WriteEngine
{
class Log;

//
// PARTIAL_EXTENT - Extent is partially filled
// EMPTY_DBROOT   - DRoot is empty (has no extents)
// EXTENT_BOUNDARY- Encountered extent boundary, add next extent
// OUT_OF_SERVICE - Extent is disabled or out-of-service
//
// Changes to this enum should be reflected in stateStrings array in
// we_dbrootextenttracker.cpp.
//
enum DBRootExtentInfoState
{
  DBROOT_EXTENT_PARTIAL_EXTENT = 1,
  DBROOT_EXTENT_EMPTY_DBROOT = 2,
  DBROOT_EXTENT_EXTENT_BOUNDARY = 3,
  DBROOT_EXTENT_OUT_OF_SERVICE = 4
};

//------------------------------------------------------------------------------
/** @brief Tracks the current DBRoot/extent we are loading.
 */
//------------------------------------------------------------------------------
struct DBRootExtentInfo
{
  uint32_t fPartition;
  uint16_t fDbRoot;
  uint16_t fSegment;
  BRM::LBID_t fStartLbid;
  HWM fLocalHwm;
  uint64_t fDBRootTotalBlocks;
  DBRootExtentInfoState fState;

  DBRootExtentInfo()
   : fPartition(0)
   , fDbRoot(0)
   , fSegment(0)
   , fStartLbid(0)
   , fLocalHwm(0)
   , fDBRootTotalBlocks(0)
   , fState(DBROOT_EXTENT_PARTIAL_EXTENT)
  {
  }

  DBRootExtentInfo(uint16_t dbRoot, uint32_t partition, uint16_t segment, BRM::LBID_t startLbid, HWM localHwm,
                   uint64_t dbrootTotalBlocks, DBRootExtentInfoState state);

  bool operator<(const DBRootExtentInfo& entry) const;
};

//------------------------------------------------------------------------------
/** @brief Class to track the order of placement (rotation) of extents as
 *  they are filled in and/or added to the DBRoots for the relevant PM.
 */
//------------------------------------------------------------------------------
class DBRootExtentTracker
{
 public:
  /** @brief DBRootExtentTracker constructor
   * @param oid Column OID of interest.
   * @param colWidths Widths (in bytes) of all the columns in the table.
   * @param dbRootHWMInfoColVec Column HWM, DBRoots, etc for this table.
   * @param columnIdx Index (into colWidths and dbRootHWMInfoColVec)
   *        referencing the column that applies to this ExtentTracker.
   * @param logger Logger to be used for logging messages.
   */
  EXPORT DBRootExtentTracker(OID oid, const std::vector<int>& colWidths,
                             const std::vector<BRM::EmDbRootHWMInfo_v>& dbRootHWMInfoColVec,
                             unsigned int columnIdx, Log* logger);

  /** @brief Select the first DBRoot/segment file to add rows to, for this PM.
   * @param dbRootExtent Dbroot/segment file selected for first set of rows.
   * @param bNoStartExtentOnThisPM Is starting HWM extent missing or disabled.
   *        If HWM extent is missing or disabled, the app will have to allo-
   *        cate a new extent (at the DBRoot returned in dbRootExtent)) in
   *        order to add any rows.
   * @param bEmptyPM  Is this PM void of any available extents
   * @return Returns NO_ERROR if success, else returns error code.
   */
  EXPORT int selectFirstSegFile(DBRootExtentInfo& dbRootExtent, bool& bNoStartExtentOnThisPM, bool& bEmptyPM,
                                std::string& errMsg);

  /** @brief Set up this Tracker to select the same first DBRoot/segment file
   * as the reference DBRootExtentTracker that is specified from a ref column.
   *
   * Application code should call selectFirstSegFile for a reference column,
   * and assignFirstSegFile for all other columns in the same table.
   * @param refTracker Tracker object used to assign first DBRoot/segment.
   * @param dbRootExtent Dbroot/segment file selected for first set of rows.
   */
  EXPORT void assignFirstSegFile(const DBRootExtentTracker& refTracker, DBRootExtentInfo& dbRootExtent);

  /** @brief Iterate/return next DBRoot to be used for the next extent.
   *
   * Case 1)
   * If it is the "very" first extent for the specified DBRoot, then the
   * applicable partition to be used for the first extent is also returned.
   *
   * Case 2)
   * If the user moves a DBRoot to a different PM, then the next cpimport.bin
   * job on the recepient PM may encounter 2 partially filled in extents.
   * This differs from the norm, where we only have 1 partially filled extent
   * at any given time, on a PM.  When a DBRoot is moved, we may finish an ex-
   * tent on 1 DBRoot, and instead of advancing to start a new extent, we ro-
   * tate to the recently moved DBRoot, and have to first fill in a partilly
   * filled in extent instead of adding a new extent.  Case 2 is intended to
   * cover this use case.
   * In this case, in the middle of an import, if the next extent to receive
   * rows is a partially filled in extent, then the DBRoot, partition, and
   * segment number for the partial extent are returned.  In addition, the
   * current HWM and starting LBID for the relevant extent are returned.
   *
   * Case 3)
   * If we are just finishing one extent and adding the next extent, then
   * only the DBRoot argument is relevant, telling us where to create the
   * next extent.  Return value will be true.  This case also applies to
   * the instance where the HWM extent for the next DBRoot is disabled.
   *
   * @param dbRoot DBRoot for the next extent
   * @param partition If first extent on dbRoot (or partial extent), then
   *        this is the partition #
   * @param segment If partially full extent, then this is the segment #
   * @param localHwm If partially full extent, then this is current HWM.
   * @param startLbid If partially full extent, then this is starting LBID of
   *         the current HWM extent.
   *
   * @return Returns true if new extent needs to be allocated, returns false
   *         if extent is partially full, and has room for more rows.
   */
  EXPORT bool nextSegFile(uint16_t& dbRoot, uint32_t& partition, uint16_t& segment, HWM& localHwm,
                          BRM::LBID_t& startLbid);

  /** @brief get the DBRootExtentInfo list
   */
  const std::vector<DBRootExtentInfo>& getDBRootExtentList();

  /** @brief get the CurrentDBRootIdx
   */
  inline int getCurrentDBRootIdx()
  {
    boost::mutex::scoped_lock lock(fDBRootExtTrkMutex);
    return fCurrentDBRootIdx;
  }

 private:
  DBRootExtentInfoState determineState(int colWidth, HWM localHwm, uint64_t dbRootTotalBlocks,
                                       int16_t status);
  // Select First DBRoot/segment file on a PM having no extents for fOID
  int selectFirstSegFileForEmptyPM(std::string& errMsg);
  void initEmptyDBRoots();  // init ExtentList for empty DBRoots
  void logFirstDBRootSelection() const;

  OID fOID;                         // applicable colunn OID
  long long fBlksPerExtent;         // blocks per extent for fOID
  Log* fLog;                        // logger
  boost::mutex fDBRootExtTrkMutex;  // mutex to access fDBRootExtentList
  int fCurrentDBRootIdx;            // Index into fDBRootExtentList,
  //   DBRoot where current extent is
  //   being added
  std::vector<DBRootExtentInfo> fDBRootExtentList;  // List of current pending
  //   DBRoot/extents for each DBRoot
  //   assigned to the local PM.
  bool fEmptyOrDisabledPM;  // true if PM has no extents or all
  //   extents are disabled
  bool fEmptyPM;  // true if PM has no available or
  //   disabled extents
  bool fDisabledHWM;  // Did job start with disabled HWM
};

}  // namespace WriteEngine

#undef EXPORT
